#!/usr/bin/env python3
"""
JH子系统最简AI聊天服务器
只包含核心聊天功能，完全独立运行
"""

from fastapi import FastAPI, HTTPException, Query, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Dict, Any, Optional, List
from datetime import datetime
from contextlib import asynccontextmanager
import uvicorn
import sys
import os
from pathlib import Path
import sqlite3
import json
import re
from bs4 import BeautifulSoup
import io
from fastapi.responses import StreamingResponse
try:
    import PyPDF2
except Exception:
    PyPDF2 = None
try:
    import docx as python_docx
except Exception:
    python_docx = None

# 添加项目根目录到路径
sys.path.insert(0, str(Path(__file__).parent))

# 尝试导入服务
try:
    from src.services.llm_api_service import LLMApiService, JHChatService
    OLLAMA_AVAILABLE = True  # 兼容字段名，代表云端API可用状态
except Exception as e:
    print(f"Ollama服务导入失败: {e}")
    OLLAMA_AVAILABLE = False

try:
    from src.jh_subsystem.job_data_service import job_data_service, JobDataFilters
    JH_DATA_AVAILABLE = True
except Exception as e:
    print(f"JH数据服务导入失败: {e}")
    JH_DATA_AVAILABLE = False

try:
    from src.jh_subsystem.intelligent_matching import (
        IntelligentMatchingEngine, UserProfile, JobPosition, MatchResult,
        SkillAssessment, SkillLevel, MatchingMode, intelligent_matching_engine
    )
    INTELLIGENT_MATCHING_AVAILABLE = True
except Exception as e:
    print(f"智能匹配服务导入失败: {e}")
    INTELLIGENT_MATCHING_AVAILABLE = False

# Pydantic 模型
class ChatMessage(BaseModel):
    message: str
    context_data: Optional[Dict[str, Any]] = None
    stream: bool = False

class ChatResponse(BaseModel):
    response: str
    timestamp: str
    error: Optional[str] = None

# JobDataFilters 从 jh_subsystem 导入，不需要重复定义

# 智能匹配相关模型
class SkillInput(BaseModel):
    name: str
    level: str = "中级"
    confidence: float = 0.8
    years_experience: int = 2
    evidence_sources: List[str] = []

class UserPreferencesInput(BaseModel):
    preferred_locations: List[str] = []
    remote_preference: float = 0.5
    salary_min: Optional[int] = None
    salary_max: Optional[int] = None
    salary_preferred: Optional[int] = None
    company_size_preference: str = "不限"
    industry_preferences: List[str] = []
    work_style_preferences: Dict[str, float] = {}
    career_goals: List[str] = []

class SmartMatchingRequest(BaseModel):
    user_id: str = "temp_user"
    name: str = "用户"
    skills: List[SkillInput] = []
    preferences: UserPreferencesInput = UserPreferencesInput()
    work_experience_years: int = 0
    education_level: str = "本科"
    current_position: Optional[str] = None
    current_salary: Optional[int] = None
    soft_skills: Dict[str, float] = {}
    personality_traits: Dict[str, float] = {}
    job_count_limit: int = 20
    matching_mode: str = "hybrid"

class FuzzyMatchingRequest(BaseModel):
    user_query: str
    job_count_limit: int = 20
    matching_mode: str = "fuzzy"

# 数据库初始化函数
async def startup_db():
    """启动时初始化数据库"""
    if JH_DATA_AVAILABLE:
        try:
            print("正在初始化JH数据库...")
            await job_data_service.initialize_database(use_real_data=True)
            print("JH数据库初始化成功!")
        except Exception as e:
            print(f"JH数据库初始化失败: {e}")

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行
    await startup_db()
    yield
    # 关闭时执行（如果需要）
    pass

# 创建应用
app = FastAPI(title="JH AI聊天服务", version="1.0.0", lifespan=lifespan)

# CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 静态文件
if os.path.exists("frontend"):
    app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend")

# 全局服务
ollama_service = None  # 将被云端API服务替代
jh_chat_service = None

@app.get("/")
async def root():
    return {
        "message": "JH AI聊天服务",
        "status": "running",
                    "ollama_available": OLLAMA_AVAILABLE,
        "jh_data_available": JH_DATA_AVAILABLE
    }

@app.get("/health")
async def health():
    return {"status": "healthy", "timestamp": datetime.now().isoformat()}

# JH数据分析API路由
@app.get("/api/jh/overview")
async def get_overview_statistics():
    """获取概览统计信息"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        stats = await job_data_service.get_overview_statistics()
        return stats
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取统计信息失败: {str(e)}")

@app.get("/api/jh/jobs")
async def get_jobs(
    page: int = Query(1, ge=1),
    page_size: int = Query(33, ge=1, le=1000),
    job_title: Optional[str] = Query(None),
    company_name: Optional[str] = Query(None),
    job_location: Optional[str] = Query(None),
    job_country: Optional[str] = Query(None),
    job_schedule_type: Optional[str] = Query(None),
    salary_min: Optional[float] = Query(None),
    salary_max: Optional[float] = Query(None),
    work_from_home: Optional[bool] = Query(None),
    skills: Optional[str] = Query(None)
):
    """获取分页职位数据"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 构建筛选条件
        filters = JobDataFilters(
            job_title=job_title,
            company_name=company_name,
            location=job_location,
            country=job_country,
            schedule_type=job_schedule_type,
            salary_min=salary_min,
            salary_max=salary_max,
            work_from_home=work_from_home,
            skills=skills.split(',') if skills else None
        )
        
        result = await job_data_service.get_jobs_paginated(page=page, page_size=page_size, filters=filters)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取职位数据失败: {str(e)}")

@app.get("/api/jh/stats")
async def get_stats():
    """兼容性统计端点"""
    return await get_overview_statistics()

@app.get("/api/jh/jobs/{job_id}")
async def get_job_detail(job_id: int):
    """获取单个岗位详细信息"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        job = await job_data_service.get_job_by_id(job_id)
        if not job:
            raise HTTPException(status_code=404, detail="岗位不存在")
        
        return {
            "status": "success",
            "data": job
        }
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取岗位详情失败: {str(e)}")

@app.get("/api/jh/filter-options")
async def get_filter_options():
    """获取筛选选项"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        return await job_data_service.get_filter_options()
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取筛选选项失败: {str(e)}")

@app.get("/api/jh/chart-data/{chart_type}")
async def get_chart_data(
    chart_type: str,
    dimension: Optional[str] = Query(None),
    limit: Optional[int] = Query(None),
    expanded: bool = Query(False)  # 新增参数，用于区分是否为展开模式
):
    """获取图表数据"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 根据expanded参数决定数据量限制
        if expanded:
            # 展开模式：显示所有数据或更多数据
            if chart_type == "jobTypeChart":
                actual_limit = None  # 不限制
            elif chart_type == "locationChart":
                actual_limit = None  # 不限制
            elif chart_type == "salaryChart":
                actual_limit = None  # 不限制
            elif chart_type == "skillsChart":
                actual_limit = None  # 不限制
            elif chart_type == "companyChart":
                actual_limit = None  # 不限制
            else:
                actual_limit = limit
        else:
            # 正常模式：使用默认限制
            if chart_type == "jobTypeChart":
                actual_limit = limit or 10
            elif chart_type == "locationChart":
                actual_limit = limit or 15
            elif chart_type == "salaryChart":
                actual_limit = limit or 10
            elif chart_type == "skillsChart":
                actual_limit = limit or 20
            elif chart_type == "companyChart":
                actual_limit = limit or 15
            else:
                actual_limit = limit or 10
        
        return await job_data_service.get_chart_data(chart_type, dimension, actual_limit)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取图表数据失败: {str(e)}")

@app.get("/api/jh/chat/status")
async def chat_status():
	"""聊天服务状态（兼容前端字段）"""
	if not OLLAMA_AVAILABLE:
		return {
			"ollama_available": False,
			"qwen_model_available": False,
			"error": "AI云端服务不可用",
			"timestamp": datetime.now().isoformat()
		}
	
	try:
		global ollama_service
		if not ollama_service:
			# 以云端API服务替代本地Ollama
			ollama_service = LLMApiService()
		
		is_available = await ollama_service.is_available()
		model_exists = await ollama_service.check_model_exists()
		
		return {
			"ollama_available": is_available,
			"qwen_model_available": model_exists,
			"available_models": [ollama_service.model_name] if is_available else [],
			"current_model": ollama_service.model_name if ollama_service else "qwen-plus",
			"timestamp": datetime.now().isoformat()
		}
	except Exception as e:
		return {
			"ollama_available": False,
			"qwen_model_available": False,
			"error": str(e),
			"timestamp": datetime.now().isoformat()
		}

@app.post("/api/jh/chat/message")
async def chat_message(chat_data: ChatMessage):
	"""处理聊天消息（改为云端API服务）"""
	if not OLLAMA_AVAILABLE:
		raise HTTPException(status_code=503, detail="AI云端服务不可用")
	
	try:
		global ollama_service, jh_chat_service
		if not ollama_service:
			ollama_service = LLMApiService()
		if not jh_chat_service:
			jh_chat_service = JHChatService(ollama_service)
		
		# 如果需要流式输出，返回SSE流
		if chat_data.stream:
			async def generate_response():
				try:
					conversation_history = jh_chat_service._get_chart_conversation_history(jh_chat_service.current_chart_id)
					conversation_history.append({"role": "user", "content": chat_data.message})
					enhanced_message = jh_chat_service._enhance_message_with_context(chat_data.message, chat_data.context_data or {})
					messages = conversation_history[-20:]
					if enhanced_message != chat_data.message:
						messages[-1]["content"] = enhanced_message
					full_response = ""
					async for chunk in jh_chat_service.llm.generate_stream(
						messages=messages,
						system_prompt=jh_chat_service._get_system_prompt()
					):
						full_response += chunk
						yield f"data: {json.dumps({'content': chunk, 'done': False}, ensure_ascii=False)}\n\n"
					conversation_history.append({"role": "assistant", "content": full_response})
					# 发送完成信号
					yield f"data: {json.dumps({'done': True}, ensure_ascii=False)}\n\n"
				except Exception as e:
					yield f"data: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
			return StreamingResponse(
				generate_response(),
				media_type="text/event-stream",
				headers={
					"Cache-Control": "no-cache",
					"Connection": "keep-alive",
					"Access-Control-Allow-Origin": "*",
					"Access-Control-Allow-Headers": "*"
				}
			)
		
		# 非流式，直接返回完整结果
		result = await jh_chat_service.chat(
			user_message=chat_data.message,
			context_data=chat_data.context_data or {},
			stream=False
		)
		
		return ChatResponse(**result)
	except Exception as e:
		raise HTTPException(status_code=500, detail=f"聊天失败: {str(e)}")

@app.post("/api/jh/chat/stream")
async def chat_message_stream(chat_data: ChatMessage):
	"""流式发送聊天消息（SSE）"""
	if not OLLAMA_AVAILABLE:
		raise HTTPException(status_code=503, detail="AI云端服务不可用")
	
	try:
		global ollama_service, jh_chat_service
		if not ollama_service:
			ollama_service = LLMApiService()
		if not jh_chat_service:
			jh_chat_service = JHChatService(ollama_service)
		
		async def generate_response():
			try:
				conversation_history = jh_chat_service._get_chart_conversation_history(jh_chat_service.current_chart_id)
				conversation_history.append({"role": "user", "content": chat_data.message})
				enhanced_message = jh_chat_service._enhance_message_with_context(chat_data.message, chat_data.context_data or {})
				messages = conversation_history[-20:]
				if enhanced_message != chat_data.message:
					messages[-1]["content"] = enhanced_message
				full_response = ""
				async for chunk in jh_chat_service.llm.generate_stream(
					messages=messages,
					system_prompt=jh_chat_service._get_system_prompt()
				):
					full_response += chunk
					yield f"data: {json.dumps({'content': chunk, 'done': False}, ensure_ascii=False)}\n\n"
				conversation_history.append({"role": "assistant", "content": full_response})
				# 发送完成信号
				yield f"data: {json.dumps({'done': True}, ensure_ascii=False)}\n\n"
			except Exception as e:
				yield f"data: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
		return StreamingResponse(
			generate_response(),
			media_type="text/event-stream",
			headers={
				"Cache-Control": "no-cache",
				"Connection": "keep-alive",
				"Access-Control-Allow-Origin": "*",
				"Access-Control-Allow-Headers": "*",
				"X-Accel-Buffering": "no"
			}
		)
	except Exception as e:
		raise HTTPException(status_code=500, detail=f"聊天失败: {str(e)}")

@app.get("/api/jh/chat/suggestions")
async def chat_suggestions():
    """聊天建议"""
    suggestions = [
        {
            "category": "简历优化",
            "questions": [
                "如何优化我的简历？",
                "简历中应该突出哪些技能？",
                "如何写好工作经历？"
            ]
        },
        {
            "category": "面试准备", 
            "questions": [
                "面试前需要准备什么？",
                "如何回答技术问题？",
                "如何展示项目经验？"
            ]
        }
    ]
    return {"suggestions": suggestions, "timestamp": datetime.now().isoformat()}

@app.post("/api/jh/resume/upload")
async def upload_resume(file: UploadFile = File(...)):
    """上传简历文件进行分析（简易服务器版本）"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    try:
        allowed_types = ['application/pdf', 'application/msword',
                         'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                         'text/html', 'text/plain']
        if file.content_type not in allowed_types:
            raise HTTPException(status_code=400, detail=f"不支持的文件类型: {file.content_type}")
        content = await file.read()
        text_content = ""
        if file.content_type == 'application/pdf':
            if not PyPDF2:
                raise HTTPException(status_code=500, detail="PDF解析依赖未安装")
            text_content = _extract_text_from_pdf(content)
        elif file.content_type in ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']:
            if not python_docx:
                raise HTTPException(status_code=500, detail="DOCX解析依赖未安装")
            text_content = _extract_text_from_docx(content)
        elif file.content_type == 'text/html':
            text_content = _extract_text_from_html(content)
        else:
            text_content = content.decode('utf-8', errors='ignore')
        analysis_result = await _analyze_resume_content(text_content)
        return {
            "filename": file.filename,
            "content_type": file.content_type,
            "file_size": len(content),
            "extracted_text_length": len(text_content),
            "analysis": analysis_result
        }
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"简历分析失败: {str(e)}")

@app.post("/api/jh/resume/manual")
async def analyze_manual_resume(resume_data: Dict[str, Any]):
    """分析手动输入的简历信息（简易服务器版本）"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    try:
        text_content = f"""
        姓名: {resume_data.get('name','')}
        邮箱: {resume_data.get('email','')}
        电话: {resume_data.get('phone','')}
        教育背景: {resume_data.get('education','')}
        工作经验: {resume_data.get('experience','')}
        技能: {', '.join(resume_data.get('skills', []))}
        个人简介: {resume_data.get('summary','')}
        目标职位: {resume_data.get('target_position','')}
        期望薪资: {resume_data.get('expected_salary','')}
        """
        analysis_result = await _analyze_resume_content(text_content)
        return {"input_type": "manual", "analysis": analysis_result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"简历分析失败: {str(e)}")

# --- 辅助函数：提取与分析 ---

def _extract_text_from_pdf(content: bytes) -> str:
    pdf_file = io.BytesIO(content)
    reader = PyPDF2.PdfReader(pdf_file)
    text = "";
    for page in reader.pages:
        text += (page.extract_text() or "") + "\n"
    return text.strip()

def _extract_text_from_docx(content: bytes) -> str:
    file_obj = io.BytesIO(content)
    doc = python_docx.Document(file_obj)
    return "\n".join([p.text for p in doc.paragraphs]).strip()

def _extract_text_from_html(content: bytes) -> str:
    soup = BeautifulSoup(content, 'html.parser')
    for s in soup(["script", "style"]):
        s.decompose()
    return soup.get_text().strip()

async def _analyze_resume_content(text_content: str) -> Dict[str, Any]:
	"""优先调用大模型进行结构化分析，失败时回退到启发式分析"""
	# 优先使用 LLM API（通过全局 ollama_service）
	if OLLAMA_AVAILABLE:
		try:
			result = await _analyze_resume_content_with_llm(text_content)
			if result and isinstance(result, dict):
				return result
		except Exception:
			# 回退到启发式
			pass
	# 回退：本地启发式分析
	return await _analyze_resume_content_heuristic(text_content)

async def _analyze_resume_content_heuristic(text_content: str) -> Dict[str, Any]:
	"""原有启发式分析实现，保留以便回退"""
	# 获取技能基准（skillsChart 与数据服务对齐）
	chart_data = await job_data_service.get_chart_data("skillsChart")
	# 动态技能目录与需求强度（出现次数）
	skill_labels = chart_data.get("labels", []) if isinstance(chart_data, dict) else []
	skill_demand_values = chart_data.get("values", []) if isinstance(chart_data, dict) else []
	demand_map = {label: float(skill_demand_values[idx]) for idx, label in enumerate(skill_labels)} if skill_labels and skill_demand_values else {}
	# 技能匹配（使用动态技能目录与需求权重，去随机性）
	skill_matches = _analyze_skills_in_text(text_content, skills_catalog=skill_labels, demand_weights=demand_map)
	competitiveness_score = _calculate_competitiveness_score(skill_matches, text_content)
	# 修正：skill_match_score 直接取技能平均分（0-100区间）
	skill_match_score = sum(skill_matches.values()) / max(len(skill_matches), 1)
	salary_competitiveness = min(skill_match_score * 1.2, 100)
	# 推荐职位
	recommended_positions = await _get_recommended_positions(skill_matches)
	# 缺失技能（优先根据市场热门技能）
	missing_skills = _identify_missing_skills(skill_matches, market_top_skills=skill_labels)
	strength_areas = _identify_strength_areas(skill_matches)
	# 目标岗位适配（若文本中包含“目标职位:”字段则尝试计算）
	target_role = _extract_target_position_from_text(text_content)
	target_role_skill_coverage = None
	missing_target_skills = []
	if target_role:
		try:
			top_role_skills = await _get_top_skills_for_role(target_role, top_n=10)
			matched = [s for s in top_role_skills if s.lower() in [k.lower() for k in skill_matches.keys()]]
			coverage = (len(matched) / max(len(top_role_skills), 1)) * 100.0 if top_role_skills else 0.0
			target_role_skill_coverage = round(coverage, 1)
			missing_target_skills = [s for s in top_role_skills if s.lower() not in [k.lower() for k in skill_matches.keys()]][:5]
		except Exception:
			pass
	return {
		"skill_matches": skill_matches,
		"competitiveness_score": round(competitiveness_score, 1),
		"skill_match_score": round(skill_match_score, 1),
		"salary_competitiveness": round(salary_competitiveness, 1),
		"career_suggestions": _generate_career_suggestions(skill_matches, text_content),
		"recommended_positions": recommended_positions,
		"missing_skills": missing_skills,
		"strength_areas": strength_areas,
		**({"target_role": target_role} if target_role else {}),
		**({"target_role_skill_coverage": target_role_skill_coverage} if target_role_skill_coverage is not None else {}),
		**({"missing_target_skills": missing_target_skills} if missing_target_skills else {}),
	}

async def _analyze_resume_content_with_llm(text_content: str) -> Dict[str, Any]:
	"""使用大模型API进行结构化简历分析，输出与前端兼容的字段"""
	global ollama_service
	if not ollama_service:
		ollama_service = LLMApiService()
	
	# 系统提示词：要求输出严格JSON（不包含额外文本）
	system_prompt = (
		"你是资深的简历分析专家。请基于用户提供的完整简历文本，进行结构化分析，并只输出JSON。不输出任何解释或前后缀。"
		"JSON 字段规范如下：\n"
		"{"
		"  \"skill_matches\": {\"<skill>\": <0-100分>},\n"
		"  \"competitiveness_score\": <0-100的数字>,\n"
		"  \"skill_match_score\": <0-100的数字>,\n"
		"  \"salary_competitiveness\": <0-100的数字>,\n"
		"  \"career_suggestions\": [\"建议1\", \"建议2\"],\n"
		"  \"missing_skills\": [\"技能A\", \"技能B\"],\n"
		"  \"strength_areas\": [\"领域A\", \"领域B\"]\n"
		"}"
	)
	user_prompt = (
		"请分析以下简历全文，识别关键技能并评分（0-100，越高越强），综合生成竞争力分、技能匹配分、薪资竞争力，列出缺失技能、优势领域与可执行的职业建议。\n\n"
		f"简历全文：\n{text_content}"
	)
	resp = await ollama_service.chat_completion(
		messages=[{"role": "user", "content": user_prompt}],
		system_prompt=system_prompt,
		temperature=0.2,
		max_tokens=1200,
	)
	content = (resp or {}).get("message", {}).get("content", "")
	parsed = _parse_structured_json(content)
	if not isinstance(parsed, dict):
		raise ValueError("LLM输出解析失败")
	
	# 兜底字段与裁剪
	skill_matches = parsed.get("skill_matches") or {}
	competitiveness_score = float(parsed.get("competitiveness_score") or 0.0)
	skill_match_score = float(parsed.get("skill_match_score") or (
		sum(skill_matches.values()) / max(len(skill_matches), 1) if skill_matches else 0.0
	))
	salary_competitiveness = float(parsed.get("salary_competitiveness") or min(skill_match_score * 1.2, 100))
	career_suggestions = parsed.get("career_suggestions") or []
	missing_skills = parsed.get("missing_skills") or []
	strength_areas = parsed.get("strength_areas") or []
	
	# 保持与前端兼容：推荐职位仍基于内部岗位数据生成
	recommended_positions = await _get_recommended_positions(skill_matches)
	
	return {
		"skill_matches": {k: float(v) for k, v in skill_matches.items()},
		"competitiveness_score": round(competitiveness_score, 1),
		"skill_match_score": round(skill_match_score, 1),
		"salary_competitiveness": round(salary_competitiveness, 1),
		"career_suggestions": career_suggestions[:8],
		"recommended_positions": recommended_positions,
		"missing_skills": missing_skills[:5],
		"strength_areas": strength_areas[:5],
	}

def _parse_structured_json(text: str) -> Dict[str, Any]:
	"""尽力从模型输出中提取严格JSON并解析。失败返回空dict"""
	import json
	text = (text or "").strip()
	if not text:
		return {}
	# 直接尝试
	try:
		return json.loads(text)
	except Exception:
		pass
	# 尝试截取第一个花括号JSON片段
	start = text.find("{")
	end = text.rfind("}")
	if start != -1 and end != -1 and end > start:
		snippet = text[start:end+1]
		try:
			return json.loads(snippet)
		except Exception:
			pass
	# 尝试替换单引号为双引号的简易修正
	fixed = text.replace("'", '"')
	try:
		return json.loads(fixed)
	except Exception:
		return {}

def _analyze_skills_in_text(text: str, skills_catalog: Optional[List[str]] = None, demand_weights: Optional[Dict[str, float]] = None) -> Dict[str, float]:
    """在文本中识别技能并打分（0-100）。
    - skills_catalog: 若提供，使用数据库动态技能列表；否则使用内置少量技能集
    - demand_weights: 技能市场需求强度（出现次数），做轻权重加成
    """
    # 内置回退技能列表（小集）
    default_skills = [
        "python", "java", "javascript", "sql", "r", "scala", "c++", "c#",
        "machine learning", "deep learning", "data analysis", "statistics",
        "tensorflow", "pytorch", "scikit-learn", "pandas", "numpy",
        "hadoop", "spark", "kafka", "docker", "kubernetes",
        "aws", "azure", "gcp", "git", "linux", "excel"
    ]
    # 采用动态或默认技能表
    skills_list = skills_catalog or default_skills
    text_lower = text.lower()
    matches: Dict[str, float] = {}

    # 预计算市场需求权重映射（归一到[0.9, 1.2]区间，避免过度放大）
    weight_map: Dict[str, float] = {}
    if demand_weights:
        values = list(demand_weights.values())
        if values:
            min_v, max_v = min(values), max(values)
            span = (max_v - min_v) or 1.0
            for skill, raw in demand_weights.items():
                norm = (raw - min_v) / span
                weight_map[skill.lower()] = 0.9 + norm * 0.3  # 0.9 ~ 1.2

    # 简单匹配计数并评分：出现次数 -> 基础分；再乘以需求权重；封顶100
    for skill in skills_list:
        key = str(skill).strip()
        if not key:
            continue
        key_lower = key.lower()
        if key_lower in text_lower:
            count = text_lower.count(key_lower)
            base = min(count * 25, 85)  # 次数对应的基础分，最多85
            weight = weight_map.get(key_lower, 1.0)
            score = min(base * weight, 100.0)
            matches[key] = round(score, 1)

    return matches

def _calculate_competitiveness_score(skill_matches: Dict[str, float], text: str) -> float:
    if not skill_matches:
        return 50.0
    avg_skill = sum(skill_matches.values()) / len(skill_matches)
    exp_bonus = 10 if ("年" in text or "year" in text.lower()) else 0
    edu_bonus = 0
    for kw in ["学士", "硕士", "博士", "bachelor", "master", "phd", "university", "college"]:
        if kw.lower() in text.lower():
            edu_bonus = 5
            break
    return min(avg_skill + exp_bonus + edu_bonus, 100)

async def _get_recommended_positions(skill_matches: Dict[str, float]) -> List[Dict[str, Any]]:
	data = await job_data_service.get_jobs_paginated(page=1, page_size=50)
	user_skill_scores = {str(k).lower(): float(v) for k, v in (skill_matches or {}).items()}
	recs: List[Dict[str, Any]] = []
	for job in data.get('jobs', []) or []:
		job_skills = job.get('job_skills') or []
		skills_original = job_skills if isinstance(job_skills, list) else []
		skills_lower = [s.strip().lower() for s in skills_original if isinstance(s, str) and s.strip()]
		matched_lower = [s for s in skills_lower if s in user_skill_scores]
		if matched_lower:
			scores = [user_skill_scores[s] for s in matched_lower]
			match_score = sum(scores) / len(scores)
		else:
			match_score = 0.0
		matched_original_order = [s for s in skills_original if isinstance(s, str) and s.strip().lower() in set(matched_lower)]
		top3 = matched_original_order[:3]
		reasons = [f"技能匹配: {', '.join(top3)}"] if top3 else []
		recs.append({
			"id": job.get("id"),
			"title": job.get('job_title_short', '') or job.get('job_title', ''),
			"company": job.get('company_name', ''),
			"location": job.get('job_location', ''),
			"match_score": round(float(match_score), 1),
			"salary_range": f"${job.get('salary_year_avg', 0):,.0f}" if job.get('salary_year_avg') else "待议",
			"tags": skills_original[:4] if isinstance(skills_original, list) else [],
			"type": job.get('job_schedule_type', '') or '全职',
			"posted_at": job.get('job_posted_date_formatted') or job.get('job_posted_date') or '',
			"match_reasons": reasons,
		})
	recs.sort(key=lambda x: x.get('match_score') or 0, reverse=True)
	return recs[:20]

def _identify_missing_skills(skill_matches: Dict[str, float], market_top_skills: Optional[List[str]] = None) -> List[str]:
    """识别缺失技能。
    - 若提供 market_top_skills，则从其中挑选尚未覆盖的前5个；否则使用基础重要技能表
    """
    if market_top_skills:
        existing_lower = {k.lower() for k in skill_matches.keys()}
        ordered = [s for s in market_top_skills if str(s).strip()]
        return [s for s in ordered if s.lower() not in existing_lower][:5]

    important = ["Python", "SQL", "Machine Learning", "Data Analysis", "Git", "Docker"]
    return [s for s in important if s.lower() not in [k.lower() for k in skill_matches.keys()]][:5]

def _identify_strength_areas(skill_matches: Dict[str, float]) -> List[str]:
    strong = [s.lower() for s, v in skill_matches.items() if v > 70]
    areas = []
    if any(s in strong for s in ["python", "java", "javascript", "c++"]):
        areas.append("编程开发")
    if any(s in strong for s in ["machine learning", "deep learning", "tensorflow"]):
        areas.append("机器学习")
    if any(s in strong for s in ["data analysis", "statistics", "sql"]):
        areas.append("数据分析")
    if any(s in strong for s in ["aws", "azure", "docker", "kubernetes"]):
        areas.append("云计算与DevOps")
    return areas

def _generate_career_suggestions(skill_matches: Dict[str, float], text: str) -> List[Any]:
    suggestions: List[Any] = []
    strong = [skill for skill, score in skill_matches.items() if score > 70]
    weak = [skill for skill, score in skill_matches.items() if score < 50]
    if strong:
        suggestions.append(f"您在 {', '.join(strong[:3])} 方面表现突出，建议继续深化这些技能")
    if weak:
        suggestions.append(f"建议加强 {', '.join(weak[:3])} 相关技能的学习")
    plan = {
        "text": "基于当前画像的提升路径",
        "short": {"goal": "补齐关键技能短板，构建最小作品集"},
        "mid": {"goal": "主导1-2个挑战性任务，输出技术分享"},
        "long": {"goal": "打造个人技术品牌，争取专家级机会"},
        "actions": [
            f"专项补强：优先学习 {', '.join(weak[:3])}" if weak else "巩固现有优势并拓展深度",
            "实践驱动：每月完成1个可展示的小项目",
            "影响力建设：技术博客/分享，每月至少1次"
        ],
        "resources": [
            "官方文档与最佳实践",
            "优质课程与开源项目",
            "行业报告与职位需求对照"
        ],
        "metrics": [
            "GitHub作品/提交记录",
            "通过目标岗位的技能面试题目",
            "内部/社区的正反馈与评价"
        ]
    }
    suggestions.append(plan)
    return suggestions

def _extract_target_position_from_text(text: str) -> Optional[str]:
    """从合成文本中尽力提取“目标职位”字段（仅用于启发式）。"""
    try:
        import re
        # 兼容中文/英文提示
        patterns = [
            r"目标职位[:：]\s*(.+)",
            r"Target\s*Position[:：]\s*(.+)",
        ]
        for pat in patterns:
            m = re.search(pat, text, flags=re.IGNORECASE)
            if m:
                return m.group(1).strip().splitlines()[0][:64]
    except Exception:
        pass
    return None

async def _get_top_skills_for_role(role: str, top_n: int = 10) -> List[str]:
    """基于岗位标题近似匹配，统计其常见技能TopN。"""
    try:
        from collections import Counter
        filters = getattr(job_data_service, "search_jobs", None)
        if filters is None:
            return []
        # 复用已有搜索能力
        jobs = await job_data_service.search_jobs(JobDataFilters(job_title=role), limit=200)
        counter: Counter = Counter()
        for job in jobs:
            for sk in job.get("job_skills", []) or []:
                s = str(sk).strip()
                if s:
                    counter[s] += 1
        return [s for s, _ in counter.most_common(top_n)]
    except Exception:
        return []

# 智能匹配相关的Pydantic模型
class SmartMatchRequest(BaseModel):
    """智能匹配请求模型"""
    user_skills: List[str] = []  # 用户技能列表
    preferred_locations: List[str] = []  # 偏好地点
    salary_range: Optional[List[int]] = None  # 薪资范围 [min, max]
    work_experience: Optional[str] = None  # 工作经验
    job_type: Optional[str] = None  # 工作类型
    keywords: Optional[str] = None  # 关键词搜索
    match_threshold: float = 0.3  # 匹配阈值

class JobMatchResult(BaseModel):
    """职位匹配结果模型"""
    job_id: str
    job_title: str
    company: str
    location: str
    salary: Optional[str]
    job_skills: List[str]
    match_score: float  # 匹配分数 0-1
    skill_match_rate: float  # 技能匹配率
    missing_skills: List[str]  # 缺失技能
    matched_skills: List[str]  # 匹配的技能
    job_description: Optional[str] = None

# 智能匹配请求模型
class IntelligentMatchRequest(BaseModel):
    requirement: str
    use_analysis_data: bool = False
    analysis_data: Optional[Dict[str, Any]] = None
    detailed_data: Optional[Dict[str, Any]] = None

@app.post("/api/jh/intelligent-matching")
async def intelligent_matching(request: IntelligentMatchRequest):
    """智能职位匹配API - 基于自然语言描述"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 解析用户需求
        requirement = request.requirement.lower()
        filters = JobDataFilters()
        
        # 处理详细需求数据
        location_filter = None
        title_keywords = []
        
        if request.detailed_data:
            detailed = request.detailed_data
            
            # 处理位置过滤
            if detailed.get('location'):
                location_filter = detailed['location']
                filters.location = location_filter
            
            # 处理职位标题
            if detailed.get('job_title'):
                title_keywords.append(detailed['job_title'])
            
            # 处理技能
            if detailed.get('skills'):
                skills = [skill.strip() for skill in detailed['skills'].split(',')]
                title_keywords.extend(skills)
                
        # 基础关键词提取
        keywords = []
        if "python" in requirement:
            keywords.append("Python")
        if "java" in requirement:
            keywords.append("Java")
        if "前端" in requirement or "frontend" in requirement:
            keywords.append("前端")
        if "后端" in requirement or "backend" in requirement:
            keywords.append("后端")
        if "数据" in requirement:
            keywords.append("数据")
        if "产品" in requirement:
            keywords.append("产品")
        if "运营" in requirement:
            keywords.append("运营")
        if "设计" in requirement:
            keywords.append("设计")
        
        # 合并关键词
        all_keywords = list(set(keywords + title_keywords))
        
        # 如果有关键词，用作职位搜索
        if all_keywords:
            filters.job_title = " ".join(all_keywords)
        
        # 获取职位数据
        all_jobs = await job_data_service.search_jobs(filters, limit=500)
        
        if not all_jobs:
            return {
                "matches": [],
                "total_count": 0,
                "avg_score": 0,
                "message": "未找到符合条件的职位"
            }
        
        # 简单匹配评分
        job_matches = []
        for job in all_jobs:
            score = 60  # 基础分
            match_reasons = []
            
            # 关键词匹配
            job_title = (job.get("job_title", "") or "").lower()
            job_desc = (job.get("job_description", "") or "").lower()
            job_location = (job.get("location", "") or "").lower()
            
            for keyword in all_keywords:
                if keyword.lower() in job_title or keyword.lower() in job_desc:
                    score += 10
                    match_reasons.append(f"匹配关键词: {keyword}")
            
            # 详细需求匹配
            if request.detailed_data:
                detailed = request.detailed_data
                
                # 位置匹配
                if detailed.get('location') and location_filter:
                    if location_filter.lower() in job_location:
                        score += 15
                        match_reasons.append(f"位置匹配: {location_filter}")
                
                # 薪资匹配
                if detailed.get('salary_min') or detailed.get('salary_max'):
                    job_salary = job.get("salary", "")
                    if job_salary:
                        try:
                            # 简单的薪资匹配逻辑
                            if detailed.get('salary_min') and f"{detailed['salary_min']}万" in job_salary:
                                score += 10
                                match_reasons.append("薪资匹配")
                        except:
                            pass
            
            # 如果使用分析数据，额外加分
            if request.use_analysis_data and request.analysis_data:
                score += 15
                match_reasons.append("结合简历分析")
            
            # 限制分数范围
            score = min(score, 100)
            
            job_match = {
                **job,
                "match_score": score,
                "match_reasons": match_reasons[:3]  # 显示匹配原因
            }
            job_matches.append(job_match)
        
        # 按匹配分数排序
        job_matches.sort(key=lambda x: x["match_score"], reverse=True)
        
        # 计算统计信息
        total_count = len(job_matches)
        avg_score = sum(job["match_score"] for job in job_matches) / total_count if total_count > 0 else 0
        
        return {
            "matches": job_matches[:50],  # 返回前50个
            "total_count": total_count,
            "avg_score": avg_score,
            "message": f"找到 {total_count} 个相关职位"
        }
        
    except Exception as e:
        print(f"智能匹配处理错误: {e}")
        raise HTTPException(status_code=500, detail="智能匹配处理失败")

@app.post("/api/jh/smart-match")
async def smart_match_jobs(request: SmartMatchRequest):
    """智能职位匹配API"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 构建搜索过滤器
        filters = JobDataFilters()
        
        # 设置位置过滤
        if request.preferred_locations:
            filters.location = request.preferred_locations[0]  # 取第一个偏好位置
        
        # 设置工作类型过滤
        if request.job_type:
            filters.job_type = request.job_type
        
        # 设置关键词搜索
        if request.keywords:
            filters.job_title = request.keywords
        
        # 获取所有符合基础条件的职位
        all_jobs = await job_data_service.search_jobs(filters, limit=1000)
        
        if not all_jobs:
            return {
                "matches": [],
                "total_count": 0,
                "user_skills": request.user_skills,
                "message": "未找到符合条件的职位"
            }
        
        # 计算匹配分数
        job_matches = []
        user_skills_set = set([skill.lower().strip() for skill in request.user_skills])
        
        for job in all_jobs:
            try:
                job_skills = job.get("job_skills", []) or []
                job_skills_set = set([skill.lower().strip() for skill in job_skills if skill])
                
                # 计算技能匹配
                if user_skills_set and job_skills_set:
                    matched_skills = user_skills_set.intersection(job_skills_set)
                    skill_match_rate = len(matched_skills) / len(user_skills_set) if user_skills_set else 0
                    missing_skills = list(job_skills_set - user_skills_set)
                else:
                    matched_skills = set()
                    skill_match_rate = 0
                    missing_skills = list(job_skills_set)
                
                # 计算综合匹配分数
                match_score = skill_match_rate
                
                # 薪资匹配加分
                if request.salary_range and len(request.salary_range) >= 2:
                    job_salary = job.get("salary", "")
                    if job_salary and _is_salary_in_range(job_salary, request.salary_range):
                        match_score += 0.2
                
                # 位置匹配加分
                if request.preferred_locations:
                    job_location = job.get("location", "")
                    for pref_loc in request.preferred_locations:
                        if pref_loc.lower() in job_location.lower():
                            match_score += 0.1
                            break
                
                # 经验匹配加分
                if request.work_experience:
                    job_desc = job.get("job_description", "") or ""
                    if request.work_experience.lower() in job_desc.lower():
                        match_score += 0.1
                
                # 确保分数在0-1范围内
                match_score = min(match_score, 1.0)
                
                # 只保留超过阈值的职位
                if match_score >= request.match_threshold:
                    job_match = JobMatchResult(
                        job_id=str(job.get("id", "")),
                        job_title=job.get("job_title", ""),
                        company=job.get("company", ""),
                        location=job.get("location", ""),
                        salary=job.get("salary", ""),
                        job_skills=job_skills,
                        match_score=round(match_score, 3),
                        skill_match_rate=round(skill_match_rate, 3),
                        missing_skills=missing_skills[:10],  # 限制数量
                        matched_skills=list(matched_skills),
                        job_description=job.get("job_description", "")
                    )
                    job_matches.append(job_match)
            
            except Exception as e:
                # 跳过有问题的职位数据
                continue
        
        # 按匹配分数排序
        job_matches.sort(key=lambda x: x.match_score, reverse=True)
        
        # 限制返回数量
        top_matches = job_matches[:50]
        
        return {
            "matches": [match.dict() for match in top_matches],
            "total_count": len(job_matches),
            "user_skills": request.user_skills,
            "match_threshold": request.match_threshold,
            "message": f"找到 {len(job_matches)} 个匹配职位，显示前 {len(top_matches)} 个"
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"智能匹配失败: {str(e)}")

def _is_salary_in_range(salary_str: str, salary_range: List[int]) -> bool:
    """判断薪资是否在指定范围内"""
    try:
        import re
        # 提取薪资数字 (支持 15-25K, 20000-30000, 15k-25k 等格式)
        numbers = re.findall(r'(\d+(?:\.\d+)?)', salary_str.lower().replace('k', '000'))
        if len(numbers) >= 2:
            min_salary = float(numbers[0])
            max_salary = float(numbers[1])
            # 检查是否有重叠
            return not (max_salary < salary_range[0] or min_salary > salary_range[1])
        elif len(numbers) == 1:
            salary = float(numbers[0])
            return salary_range[0] <= salary <= salary_range[1]
    except:
        pass
    return False

@app.get("/api/jh/smart-match/suggestions")
async def get_match_suggestions():
    """获取智能匹配的建议选项"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 获取常用技能
        common_skills = await job_data_service.get_common_skills(limit=100)
        
        # 获取热门地点
        location_stats = await job_data_service.get_chart_data("locationChart", limit=20)
        popular_locations = [item["name"] for item in location_stats.get("data", [])]
        
        # 获取工作类型
        job_type_stats = await job_data_service.get_chart_data("jobTypeChart", limit=15)
        job_types = [item["name"] for item in job_type_stats.get("data", [])]
        
        return {
            "common_skills": common_skills,
            "popular_locations": popular_locations,
            "job_types": job_types,
            "experience_levels": ["应届生", "1-3年", "3-5年", "5-10年", "10年以上"],
            "salary_ranges": [
                {"label": "5K以下", "range": [0, 5000]},
                {"label": "5K-10K", "range": [5000, 10000]},
                {"label": "10K-15K", "range": [10000, 15000]},
                {"label": "15K-25K", "range": [15000, 25000]},
                {"label": "25K-35K", "range": [25000, 35000]},
                {"label": "35K以上", "range": [35000, 100000]}
            ]
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取建议选项失败: {str(e)}")

# AI智能匹配相关的数据模型
class RequirementAnalysis(BaseModel):
    """需求分析结果"""
    location_preference: Dict[str, Any] = {}  # 工作地点偏好
    skill_requirements: Dict[str, Any] = {}   # 技能要求
    salary_expectations: Dict[str, Any] = {}  # 薪资期望
    work_style: Dict[str, Any] = {}          # 工作方式偏好
    industry_preference: Dict[str, Any] = {} # 行业偏好
    career_level: Dict[str, Any] = {}        # 职业层级
    company_culture: Dict[str, Any] = {}     # 公司文化偏好

class AIMatchResult(BaseModel):
    """AI匹配结果"""
    job_data: Dict[str, Any]
    overall_score: float
    dimension_scores: Dict[str, float]
    match_reasons: List[str]
    improvement_suggestions: List[str]

class AIIntelligentMatchRequest(BaseModel):
    """AI智能匹配请求"""
    requirement: str
    use_analysis_data: bool = False
    analysis_data: Optional[Dict[str, Any]] = None
    detailed_data: Optional[Dict[str, Any]] = None

async def analyze_user_requirements_with_ai(requirement: str, analysis_data: Optional[Dict[str, Any]] = None, detailed_data: Optional[Dict[str, Any]] = None) -> RequirementAnalysis:
    """使用AI分析用户需求的各个维度"""
    try:
        # 初始化LLM服务
        llm_service = LLMApiService()
        
        # 构建系统提示词
        system_prompt = """你是一个专业的职位匹配分析师。请深度分析用户的求职需求，提取出各个维度的偏好信息。
分析维度包括：
1. 工作地点偏好（location_preference）：如远程办公、特定城市、不限地点等
2. 技能要求（skill_requirements）：用户提到的技能、经验要求等
3. 薪资期望（salary_expectations）：薪资范围、福利要求等  
4. 工作方式偏好（work_style）：如远程、混合、现场办公等
5. 行业偏好（industry_preference）：期望的行业领域
6. 职业层级（career_level）：如初级、中级、高级、管理层等
7. 公司文化偏好（company_culture）：如创业公司、大厂、外企等

请以JSON格式输出分析结果，每个维度包含：
- category: 维度类别
- preference: 具体偏好描述
- importance: 重要性评分(1-10)
- keywords: 相关关键词列表

特别注意：如果用户提供了结构化的详细需求数据，请重点分析这些结构化信息，并结合自然语言描述进行综合分析。

输出格式：
{
  "location_preference": {"category": "工作地点", "preference": "偏好描述", "importance": 8, "keywords": ["关键词1", "关键词2"]},
  "skill_requirements": {"category": "技能要求", "preference": "偏好描述", "importance": 9, "keywords": ["关键词1", "关键词2"]},
  ...
}"""

        # 构建用户消息
        user_message = f"请分析以下求职需求：\n{requirement}"
        
        # 如果有详细需求数据，加入分析
        if detailed_data:
            user_message += f"\n\n用户详细需求数据：\n{json.dumps(detailed_data, ensure_ascii=False, indent=2)}"
        
        # 如果有简历分析数据，也加入分析
        if analysis_data:
            user_message += f"\n\n用户简历分析数据：\n{json.dumps(analysis_data, ensure_ascii=False, indent=2)}"

        # 调用AI分析
        response = await llm_service.chat_completion(
            messages=[{"role": "user", "content": user_message}],
            system_prompt=system_prompt,
            temperature=0.3,
            max_tokens=1500
        )
        
        content = response.get("message", {}).get("content", "{}")
        
        # 解析AI返回的JSON
        try:
            analysis_result = json.loads(content)
            return RequirementAnalysis(**analysis_result)
        except json.JSONDecodeError:
            # 如果JSON解析失败，使用正则表达式提取关键信息
            return _fallback_requirement_analysis(requirement)
            
    except Exception as e:
        print(f"AI需求分析失败: {e}")
        return _fallback_requirement_analysis(requirement)

def _fallback_requirement_analysis(requirement: str) -> RequirementAnalysis:
    """备用的需求分析方法（基于关键词匹配）"""
    requirement_lower = requirement.lower()
    
    # 工作地点分析
    location_pref = {}
    if any(word in requirement_lower for word in ["远程", "居家", "在家", "remote", "wfh"]):
        location_pref = {"category": "工作地点", "preference": "远程办公", "importance": 8, "keywords": ["远程", "remote"]}
    elif any(city in requirement_lower for city in ["北京", "上海", "深圳", "广州", "杭州"]):
        for city in ["北京", "上海", "深圳", "广州", "杭州"]:
            if city in requirement_lower:
                location_pref = {"category": "工作地点", "preference": f"{city}地区", "importance": 7, "keywords": [city]}
                break
    
    # 技能要求分析
    skill_keywords = []
    common_skills = ["python", "java", "javascript", "react", "vue", "数据分析", "机器学习", "前端", "后端", "全栈"]
    for skill in common_skills:
        if skill in requirement_lower:
            skill_keywords.append(skill)
    
    skill_req = {}
    if skill_keywords:
        skill_req = {"category": "技能要求", "preference": f"需要{', '.join(skill_keywords)}技能", "importance": 9, "keywords": skill_keywords}
    
    # 薪资期望分析
    salary_pref = {}
    salary_pattern = r'(\d+)[-~到至](\d+)[万k]'
    salary_match = re.search(salary_pattern, requirement_lower)
    if salary_match:
        min_salary, max_salary = salary_match.groups()
        salary_pref = {"category": "薪资期望", "preference": f"{min_salary}-{max_salary}万", "importance": 8, "keywords": [f"{min_salary}万", f"{max_salary}万"]}
    
    return RequirementAnalysis(
        location_preference=location_pref,
        skill_requirements=skill_req,
        salary_expectations=salary_pref,
        work_style={},
        industry_preference={},
        career_level={},
        company_culture={}
    )

async def ai_match_jobs_with_analysis(requirement_analysis: RequirementAnalysis, jobs: List[Dict]) -> List[AIMatchResult]:
    """基于需求分析结果对职位进行AI匹配"""
    results = []
    
    for job in jobs:
        # 计算各维度匹配分数
        dimension_scores = {}
        match_reasons = []
        improvement_suggestions = []
        
        # 1. 工作地点匹配
        location_score = _calculate_location_match(requirement_analysis.location_preference, job)
        if location_score > 0:
            dimension_scores["location"] = location_score
            if location_score > 0.7:
                match_reasons.append(f"工作地点高度匹配: {requirement_analysis.location_preference.get('preference', '')}")
        
        # 2. 技能匹配
        skill_score = _calculate_skill_match(requirement_analysis.skill_requirements, job)
        if skill_score > 0:
            dimension_scores["skills"] = skill_score
            if skill_score > 0.8:
                match_reasons.append(f"技能要求高度匹配: {requirement_analysis.skill_requirements.get('preference', '')}")
            elif skill_score < 0.5:
                improvement_suggestions.append("建议提升相关技能以更好匹配此职位")
        
        # 3. 薪资匹配
        salary_score = _calculate_salary_match(requirement_analysis.salary_expectations, job)
        if salary_score > 0:
            dimension_scores["salary"] = salary_score
            if salary_score > 0.8:
                match_reasons.append(f"薪资期望符合: {requirement_analysis.salary_expectations.get('preference', '')}")
        
        # 4. 工作方式匹配
        work_style_score = _calculate_work_style_match(requirement_analysis.work_style, job)
        if work_style_score > 0:
            dimension_scores["work_style"] = work_style_score
        
        # 5. 行业匹配
        industry_score = _calculate_industry_match(requirement_analysis.industry_preference, job)
        if industry_score > 0:
            dimension_scores["industry"] = industry_score
        
        # 计算综合匹配分数（加权平均）
        weights = {
            "skills": 0.35,
            "salary": 0.25,
            "location": 0.20,
            "work_style": 0.10,
            "industry": 0.10
        }
        
        overall_score = 0
        total_weight = 0
        for dimension, score in dimension_scores.items():
            weight = weights.get(dimension, 0.1)
            overall_score += score * weight
            total_weight += weight
        
        if total_weight > 0:
            overall_score = overall_score / total_weight
        else:
            overall_score = 0.5  # 默认分数
        
        # 添加基础匹配原因
        if not match_reasons:
            match_reasons.append("基于综合条件匹配")
        
        results.append(AIMatchResult(
            job_data=job,
            overall_score=overall_score,
            dimension_scores=dimension_scores,
            match_reasons=match_reasons[:3],  # 最多3个推荐理由
            improvement_suggestions=improvement_suggestions[:2]  # 最多2个改进建议
        ))
    
    # 按综合分数排序
    results.sort(key=lambda x: x.overall_score, reverse=True)
    return results

def _calculate_location_match(location_pref: Dict, job: Dict) -> float:
    """计算工作地点匹配分数"""
    if not location_pref or not location_pref.get('keywords'):
        return 0.5  # 无明确偏好时给中等分数
    
    job_location = (job.get("location", "") or "").lower()
    job_remote = job.get("job_work_from_home", 0) == 1
    
    keywords = [kw.lower() for kw in location_pref.get('keywords', [])]
    
    # 检查远程办公匹配
    if any(kw in ["远程", "remote", "居家"] for kw in keywords):
        if job_remote:
            return 1.0
        else:
            return 0.3
    
    # 检查城市匹配
    for keyword in keywords:
        if keyword in job_location:
            return 0.9
    
    return 0.2

def _get_skill_variants(skill: str) -> List[str]:
    """获取技能的变体形式"""
    skill_variants = {
        'python': ['py', 'python3', 'python2'],
        'javascript': ['js', 'es6', 'ecmascript', 'node'],
        'typescript': ['ts'],
        'java': ['jdk', 'jvm'],
        'react': ['reactjs', 'react.js'],
        'vue': ['vuejs', 'vue.js'],
        'angular': ['angularjs'],
        'mysql': ['sql'],
        'postgresql': ['postgres', 'psql'],
        'mongodb': ['mongo'],
        'redis': ['cache'],
        'docker': ['container'],
        'kubernetes': ['k8s'],
        'frontend': ['前端', 'fe'],
        'backend': ['后端', 'be'],
        'fullstack': ['全栈'],
        'remote': ['远程', '在家'],
        'senior': ['高级', '资深'],
        'junior': ['初级', '新手'],
        'machine learning': ['ml', '机器学习'],
        'artificial intelligence': ['ai', '人工智能']
    }
    
    variants = skill_variants.get(skill.lower(), [])
    # 添加原技能名的部分匹配
    if len(skill) > 3:
        variants.append(skill[:3])  # 前3个字符
    return variants

def _calculate_skill_match(skill_req: Dict, job: Dict) -> float:
    """计算技能匹配分数 - 优化版本，降低严格度"""
    if not skill_req or not skill_req.get('keywords'):
        return 0.6  # 提高默认分数
    
    job_title = (job.get("job_title", "") or "").lower()
    job_desc = (job.get("job_description", "") or "").lower()
    job_skills = job.get("job_skills", []) or []
    job_skills_text = " ".join([str(skill).lower() for skill in job_skills])
    
    required_keywords = [kw.lower() for kw in skill_req.get('keywords', [])]
    job_content = f"{job_title} {job_desc} {job_skills_text}"
    
    matched_count = 0
    partial_matches = 0
    
    for keyword in required_keywords:
        if keyword in job_content:
            matched_count += 1
        else:
            # 检查部分匹配（如python -> py, javascript -> js）
            partial_keywords = _get_skill_variants(keyword)
            for variant in partial_keywords:
                if variant in job_content:
                    partial_matches += 0.5
                    break
    
    if not required_keywords:
        return 0.6
    
    # 计算匹配分数，包含部分匹配
    total_score = (matched_count + partial_matches) / len(required_keywords)
    
    # 给基础分数，即使没有完全匹配也有一定分数
    base_score = 0.3
    return min(1.0, base_score + total_score * 0.7)

def _calculate_salary_match(salary_exp: Dict, job: Dict) -> float:
    """计算薪资匹配分数"""
    if not salary_exp or not salary_exp.get('keywords'):
        return 0.5
    
    job_salary = job.get("salary", "") or ""
    if not job_salary:
        return 0.4  # 无薪资信息时给较低分数
    
    # 简单的薪资匹配逻辑
    salary_keywords = salary_exp.get('keywords', [])
    for keyword in salary_keywords:
        if keyword in job_salary:
            return 0.8
    
    return 0.3

def _calculate_work_style_match(work_style: Dict, job: Dict) -> float:
    """计算工作方式匹配分数"""
    if not work_style:
        return 0.5
    
    job_remote = job.get("job_work_from_home", 0) == 1
    keywords = [kw.lower() for kw in work_style.get('keywords', [])]
    
    if "远程" in keywords or "remote" in keywords:
        return 1.0 if job_remote else 0.2
    
    return 0.5

def _calculate_industry_match(industry_pref: Dict, job: Dict) -> float:
    """计算行业匹配分数"""
    if not industry_pref:
        return 0.5
    
    # 这里可以根据实际的行业分类进行匹配
    return 0.5

@app.post("/api/jh/ai-intelligent-matching")
async def ai_intelligent_matching(request: AIIntelligentMatchRequest):
    """AI智能职位匹配API - 基于AI分析的多维度匹配"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 1. 使用AI分析用户需求
        print(f"开始AI需求分析: {request.requirement[:100]}...")
        requirement_analysis = await analyze_user_requirements_with_ai(
            request.requirement, 
            request.analysis_data if request.use_analysis_data else None,
            request.detailed_data
        )
        
        # 2. 获取职位数据进行匹配
        filters = JobDataFilters()
        
        # 根据分析结果设置过滤器
        if requirement_analysis.location_preference.get('keywords'):
            location_keywords = requirement_analysis.location_preference['keywords']
            if any(kw in ["远程", "remote"] for kw in location_keywords):
                # 如果偏好远程，获取所有职位后过滤
                pass
            else:
                # 设置地点过滤
                for keyword in location_keywords:
                    if keyword in ["北京", "上海", "深圳", "广州", "杭州"]:
                        filters.location = keyword
                        break
        
        if requirement_analysis.skill_requirements.get('keywords'):
            skill_keywords = requirement_analysis.skill_requirements['keywords']
            # 降低匹配严格度：只使用第一个关键词，而不是全部关键词
            if skill_keywords:
                filters.job_title = skill_keywords[0]
        
        # 获取职位数据 - 优化匹配策略
        all_jobs = await job_data_service.search_jobs(filters, limit=500)
        
        # 如果过滤后结果太少，降低过滤条件重新搜索
        if len(all_jobs) < 20:
            print(f"初始匹配结果较少({len(all_jobs)})，降低过滤条件重新搜索...")
            # 清除技能过滤，只保留地点过滤
            relaxed_filters = JobDataFilters()
            if filters.location:
                relaxed_filters.location = filters.location
            all_jobs = await job_data_service.search_jobs(relaxed_filters, limit=1000)
            
        # 如果还是太少，完全放开过滤
        if len(all_jobs) < 10:
            print(f"放开所有过滤条件，获取更多职位进行匹配...")
            all_jobs = await job_data_service.search_jobs(JobDataFilters(), limit=1500)
        
        if not all_jobs:
            return {
                "matches": [],
                "total_count": 0,
                "avg_score": 0,
                "message": "未找到符合条件的职位",
                "requirement_analysis": requirement_analysis.dict()
            }
        
        # 3. 使用AI进行智能匹配
        print(f"开始AI智能匹配，职位数量: {len(all_jobs)}")
        ai_match_results = await ai_match_jobs_with_analysis(requirement_analysis, all_jobs)
        
        # 4. 转换为返回格式
        matches = []
        for result in ai_match_results[:50]:  # 返回前50个最佳匹配
            job_match = {
                **result.job_data,
                "match_score": int(result.overall_score * 100),  # 转换为百分制
                "match_reasons": result.match_reasons,
                "improvement_suggestions": result.improvement_suggestions,
                "dimension_scores": {k: int(v * 100) for k, v in result.dimension_scores.items()}
            }
            matches.append(job_match)
        
        # 5. 计算统计信息
        total_count = len(matches)
        avg_score = sum(match["match_score"] for match in matches) / total_count if total_count > 0 else 0
        
        return {
            "matches": matches,
            "total_count": total_count,
            "avg_score": avg_score,
            "message": f"AI智能匹配找到 {total_count} 个相关职位",
            "requirement_analysis": {
                "location_preference": requirement_analysis.location_preference,
                "skill_requirements": requirement_analysis.skill_requirements,
                "salary_expectations": requirement_analysis.salary_expectations,
                "work_style": requirement_analysis.work_style,
                "industry_preference": requirement_analysis.industry_preference,
                "career_level": requirement_analysis.career_level,
                "company_culture": requirement_analysis.company_culture
            }
        }
        
    except Exception as e:
        print(f"AI智能匹配处理错误: {e}")
        raise HTTPException(status_code=500, detail=f"AI智能匹配处理失败: {str(e)}")

if __name__ == "__main__":
    # Windows编码兼容性
    import sys
    if sys.platform == "win32":
        import os
        os.system("chcp 65001 >nul 2>&1")
    
    print("JH AI聊天服务器启动中...")
    print("="*50)
    if OLLAMA_AVAILABLE:
        print("Ollama服务模块加载成功")
    else:
        print("Ollama服务模块加载失败")
    if JH_DATA_AVAILABLE:
        print("JH数据服务模块加载成功")
    else:
        print("JH数据服务模块加载失败")
    print("="*50)
    
    uvicorn.run(
        app,
        host="0.0.0.0",
        port=8000,  # 使用8000端口避免冲突
        log_level="info"
    ) 